package service

import (
	"errors"
	"fmt"
	"github.com/chicken-team-outside/chicken_transmission/model"
	"github.com/golang/protobuf/proto"
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/util"
	"io"
)

var taskIdKey = []byte("taskId")
var currentTaskId int64

func UpdateStatus(info *model.Task, status model.TaskStatus, msg string) error {
	info.Status = status
	info.Msg = msg
	return ReplaceTask(info)
}

func initTask() (err error) {
	currentTaskId, err = GetInt64(taskIdKey)
	return nil
}

func AddTasks(tasks []*model.Task) error {
	return DbWithBatch(func(batch *leveldb.Batch) (err error) {
		for _, task := range tasks {
			currentTaskId++
			task.Id = currentTaskId
			task.CreateAt = UnixMilli()
			task.UpdateAt = UnixMilli()
			key := formatTaskKey(task.TaskType, task.Id)
			err = PutProto(batch, key, task)
			if err != nil {
				return
			}
		}
		PutInt64(batch, taskIdKey, currentTaskId)
		return
	})
}

func formatTaskKey(taskType model.TaskType, id int64) []byte {
	return []byte(fmt.Sprintf("task_%d_%d", taskType, id))
}

func getTask(id int64, taskType model.TaskType) (*model.Task, error) {
	key := formatTaskKey(taskType, id)
	task := &model.Task{}
	err := GetProto(key, task)
	if err != nil {
		return nil, err
	}
	return task, nil
}

func bytesToTask(bVal []byte) (task *model.Task, err error) {
	task = &model.Task{}
	err = proto.Unmarshal(bVal, task)
	return
}

func ExistsSameTask(task *model.Task) (bool, error) {
	prefix := []byte(fmt.Sprintf("task_%d_", task.TaskType))
	iterator := db.NewIterator(util.BytesPrefix(prefix), nil)
	for iterator.Next() {
		task1, err := bytesToTask(iterator.Value())
		if err != nil {
			return false, err
		}
		if task1.DeviceId == task.DeviceId && task1.DevicePath == task.DevicePath && task1.LocalPath == task.LocalPath {
			return true, nil
		}
	}
	return false, iterator.Error()
}

func ReplaceTask(task *model.Task) error {
	key := fmt.Sprintf("task_%d_%d", task.TaskType, task.Id)
	task.UpdateAt = UnixMilli()
	return SaveProto([]byte(key), task)
}

func walkAllTaskWithPrefix(prefix []byte, f func(key []byte, task *model.Task) error) error {
	iterator := db.NewIterator(util.BytesPrefix(prefix), nil)
	defer iterator.Release()
	for iterator.Next() {
		task := new(model.Task)
		value := iterator.Value()
		err := proto.Unmarshal(value, task)
		if err != nil {
			return err
		}
		err = f(iterator.Key(), task)
		if err != nil {
			return err
		}
	}
	return iterator.Error()
}

func walkAllTask(f func(key []byte, task *model.Task) error) error {
	return walkAllTaskWithPrefix([]byte("task_"), f)
}

func UpdateAllTaskStatus(taskTypes []model.TaskType, oldStatus []model.TaskStatus, newStatus model.TaskStatus, msg string) error {
	taskTypesSet := NewISet[model.TaskType]().Add(taskTypes...)
	oldStatusSet := NewISet[model.TaskStatus]().Add(oldStatus...)
	return DbWithBatch(func(batch *leveldb.Batch) error {
		return walkAllTask(func(key []byte, task *model.Task) error {
			if taskTypesSet.Contains(task.TaskType) && oldStatusSet.Contains(task.Status) {
				task.Status = newStatus
				task.Msg = msg
				keyClone := make([]byte, len(key))
				copy(keyClone, key)
				err := PutProto(batch, keyClone, task)
				if err != nil {
					return err
				}
			}
			return nil
		})
	})
}

func UpdateTasksStatus(taskType model.TaskType, oldStatus []model.TaskStatus, newStatus model.TaskStatus, msg string, ids []int64) error {
	oldStatusSet := NewISet[model.TaskStatus]().Add(oldStatus...)
	return DbWithBatch(func(batch *leveldb.Batch) (err error) {
		var task *model.Task
		for _, id := range ids {
			task, err = getTask(id, taskType)
			if err != nil {
				if errors.Is(err, leveldb.ErrNotFound) {
					err = nil
					continue
				}
				return
			}
			if oldStatusSet.Contains(task.Status) {
				task.Status = newStatus
				task.Msg = msg
				err = PutProto(batch, formatTaskKey(taskType, id), task)
				if err != nil {
					return
				}
			}
		}
		return
	})
}

func UpdateTaskPausedSubTask(taskType model.TaskType, ids []int64, pausedSubTask bool) error {
	return DbWithBatch(func(batch *leveldb.Batch) error {
		for _, id := range ids {
			task, err := getTask(id, taskType)
			if err != nil {
				if errors.Is(err, leveldb.ErrNotFound) {
					err = nil
					continue
				}
				return err
			}
			task.PausedSubTask = pausedSubTask
			err = PutProto(batch, formatTaskKey(taskType, id), task)
			if err != nil {
				return err
			}
		}
		return nil
	})
}

func GetOneWaitingTask(taskType model.TaskType) (task *model.Task, err error) {
	err = walkAllTaskWithPrefix([]byte(fmt.Sprintf("task_%d_", taskType)), func(key []byte, task1 *model.Task) error {
		if task1.Status == model.TaskStatus_Waiting {
			task = task1
			return io.EOF
		}
		return nil
	})
	if task != nil {
		err = nil
		return
	}
	return
}

func DeleteTaskByIds(taskType model.TaskType, ids []int64) error {
	return DbWithBatch(func(batch *leveldb.Batch) error {
		for _, id := range ids {
			batch.Delete(formatTaskKey(taskType, id))
		}
		return nil
	})
}

func FindTaskInfosByStatus(taskType model.TaskType, status []model.TaskStatus, ids []int64) (tasks []*model.Task, err error) {
	statusSet := NewISet[model.TaskStatus]().Add(status...)
	for _, id := range ids {
		var task *model.Task
		task, err = getTask(id, taskType)
		if err != nil {
			if errors.Is(err, leveldb.ErrNotFound) {
				err = nil
				continue
			}
			return
		}
		if statusSet.Contains(task.Status) {
			tasks = append(tasks, task)
		}
	}
	return
}

func FindTasksByStatus(taskType model.TaskType, status []model.TaskStatus, ids []int64) (taskIds []int64, err error) {
	statusSet := NewISet[model.TaskStatus]().Add(status...)
	var task *model.Task
	for _, id := range ids {
		task, err = getTask(id, taskType)
		if err != nil {
			if errors.Is(err, leveldb.ErrNotFound) {
				err = nil
				continue
			}
			return
		}
		if statusSet.Contains(task.Status) {
			taskIds = append(taskIds, task.Id)
		}
	}
	return
}

func FindAllTaskIdsByParentIds(taskType model.TaskType, parentIds []int64) (ids []int64, err error) {
	idsSet := NewISet[int64]().Add(parentIds...)
	err = walkAllTaskWithPrefix([]byte(fmt.Sprintf("task_%d_", taskType)), func(key []byte, task *model.Task) error {
		if idsSet.Contains(task.ParentId) {
			ids = append(ids, task.Id)
		}
		return nil
	})
	return
}

func FindAllTask() (result []*model.Task, err error) {
	err = walkAllTask(func(key []byte, task *model.Task) error {
		result = append(result, task)
		return nil
	})
	return
}
